package cn.buk.tms.dao;

import cn.buk.common.sc.Page;
import cn.buk.tms.entity.bill.*;
import cn.buk.tms.factory.OperationLogFactory;
import cn.buk.tms.sc.SearchSettlementCriteria;
import cn.buk.common.util.DateUtil;
import cn.buk.tms.entity.misc.*;
import jakarta.persistence.criteria.*;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.stereotype.Component;

import jakarta.persistence.EntityManager;
import jakarta.transaction.Transactional;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static cn.buk.tms.entity.bill.BillInfo.CUSTOMER_PAYMENT_RECEIVED;
import static cn.buk.tms.common.constant.TmsOrderConstant.BILL_NOT_AUDITED;
import static cn.buk.tms.common.constant.TmsOrderConstant.OPERATING_OBJECT_SETTLEMENT;

/**
 * @author yfdai
 */
@Component
@Transactional
public class SettlementDaoImpl extends AbstractDao implements SettlementDao {

  private static final Logger logger = LogManager.getLogger(SettlementDaoImpl.class);

  /**
   * 创建结算单
   * @param customerId     0-表示是散客结算单，>0的表示具体某个月结客户的
   * @param settlementId   =0 表示新建，>0 表示加入该结算单
   */
  @Override
  public int createSettlement(final int enterpriseId, final String username, final int customerId,
                              final int settlementId, final String settlementNo, final String settlementName,
                              final double total, final Integer[] billIds0, String remark) {
    // 新建结算单时金额不再限制
    if (settlementId == 0) {
      return createNewSettlement(enterpriseId, username, customerId, settlementNo, settlementName, total, billIds0, remark);
    } else {
      //结算单金额>=0的，允许追加账单
      return appendSettlement(enterpriseId, username, customerId, settlementId, total, billIds0);
    }
  }


  /**
   * 查找结算单
   */
  @Override
  public List<SettlementSheet> searchSettlements(int enterpriseId, SearchSettlementCriteria sc, List<Integer> allowedCustomerIds) {
    List<SettlementSheet> results = null;

    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery<SettlementSheet> cq = cb.createQuery(SettlementSheet.class);
    Root<SettlementSheet> root = cq.from(SettlementSheet.class);
    root.alias("o");

    var predicates0 = getPredicates_searchSettlements(enterpriseId, sc, allowedCustomerIds, cb, root);


    //计算根据条件查询得出的数据总数
    CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
    Root<SettlementSheet> countRoot = countQuery.from(SettlementSheet.class);
    countRoot.alias("o");

    var predicates1 = getPredicates_searchSettlements(enterpriseId, sc, allowedCustomerIds, cb, countRoot);

    countQuery.select(cb.count(countRoot)).where(predicates1.toArray(new Predicate[predicates1.size()]));
    Long count = em.createQuery(countQuery).getSingleResult();

    int maxResults = count.intValue();
    if (maxResults > 0) {
      cq.where(predicates0.toArray(new Predicate[predicates0.size()]));
      List<Order> orderByes = new ArrayList<>();
      if (sc.getOrderBy() == 0) {
        orderByes.add(cb.desc(root.get("lastUpdate")));
      } else if (sc.getOrderBy() == 1) {
        orderByes.add(cb.asc(root.get("lastUpdate")));
      } else if (sc.getOrderBy() == 2) {
        orderByes.add(cb.desc(root.get("createTime")));
      } else if (sc.getOrderBy() == 4) {
        orderByes.add(cb.asc(root.get("createTime")));
      }
      orderByes.add(cb.desc(root.get("id")));
      cq.orderBy(orderByes);

      Page page = sc.getPage();
      page.setRowCount(maxResults);

      results = em.createQuery(cq)
              .setFirstResult(page.getFirstPosition())
              .setMaxResults(page.getPageSize()).getResultList();
    }

    return results == null ? new ArrayList<>() : results;
  }

  private List<Predicate> getPredicates_searchSettlements(int enterpriseId, SearchSettlementCriteria sc, List<Integer> allowedCustomerIds, CriteriaBuilder cb, Root<SettlementSheet> root) {
    List<Predicate> predicates = new ArrayList<>();

    Predicate where = cb.conjunction();
    predicates.add(where);

    where = cb.equal(root.get(SettlementSheet_.enterpriseId), enterpriseId);
    predicates.add(where);


    List<Integer> customerIds = searchCustomerIdsLikeName(enterpriseId, sc.getLikeCustomerName());

    if (sc.getCustomerId() == 0) {
      //散客
      where = cb.equal(root.get(SettlementSheet_.customerType), 0);
      predicates.add(where);
    } else if (sc.getCustomerId() > 0) {
      where = cb.equal(root.get(SettlementSheet_.customer), sc.getCustomerId());
      predicates.add(where);
    } else if (!customerIds.isEmpty()) {
      // 查找指定的一批客户
      where = cb.in(root.get(SettlementSheet_.customer).get("id")).value(customerIds);
      predicates.add(where);
    }

    if (sc.getBeginDate() != null && sc.getEndDate() != null) {
      where = cb.greaterThanOrEqualTo(root.get(SettlementSheet_.createTime), sc.getBeginDate());
      where = cb.and(where, cb.lessThanOrEqualTo(root.get(SettlementSheet_.createTime), DateUtil.addDays(sc.getEndDate(), 1)));
      predicates.add(where);
    } else if (sc.getBeginDate() != null) {
      where = cb.greaterThanOrEqualTo(root.get(SettlementSheet_.createTime), sc.getBeginDate());
      predicates.add(where);
    } else if (sc.getEndDate() != null) {
      where = cb.lessThanOrEqualTo(root.get(SettlementSheet_.createTime), DateUtil.addDays(sc.getEndDate(), 1));
      predicates.add(where);
    }

    if (sc.getStatus() == 0 || sc.getStatus() == 1) {
      // 未支付 或 已支付
      where = cb.equal(root.get(SettlementSheet_.paid), sc.getStatus());
      predicates.add(where);
    }

    if (sc.getSettlementNo().length() > 0) {
      where = cb.like(root.get(SettlementSheet_.settlementNo), "%" + sc.getSettlementNo() + "%");
      predicates.add(where);
    } else {
      where = cb.notEqual(root.get(SettlementSheet_.amount), 0);
      predicates.add(where);
    }

    if (sc.getAmount() > 0) {
      where = cb.equal(root.get(SettlementSheet_.amount), sc.getAmount());
      predicates.add(where);
    }

    if (allowedCustomerIds != null && !allowedCustomerIds.isEmpty()) {
      // 只允许查看指定的企业客户的
      where = cb.in(root.get(SettlementSheet_.customer).get("id")).value(allowedCustomerIds);
      predicates.add(where);
    }
    return predicates;
  }


  /**
   * 查找未付款的结算单
   */
  @Override
  public List<SettlementSheet> searchUnpaidSettlements(final int enterpriseId, final int customerId) {
    if (customerId == 0) {
      // 散客结算单
      return em.createQuery("select o from SettlementSheet o " +
              "where o.enterpriseId = :enterpriseId and o.customerType = :customerType and o.paid = 0 and o.amount <> 0 " +
              "order by o.id", SettlementSheet.class)
              .setParameter("enterpriseId", enterpriseId)
              .setParameter("customerType", 0)
              .getResultList();
    } else {
      return em.createQuery("select o from SettlementSheet o " +
              "where o.enterpriseId = :enterpriseId and o.customer.id = :customerId and o.paid = 0 and o.amount <> 0 " +
              "order by o.id", SettlementSheet.class)
              .setParameter("enterpriseId", enterpriseId)
              .setParameter("customerId", customerId)
              .getResultList();
    }
  }

  @Override
  public SettlementSheet getSettlementSheetById(int id) {
    SettlementSheet ss = em.find(SettlementSheet.class, id);
    if (ss != null) {
      em.refresh(ss);
    }

    return ss;
  }


  @Override
  public SettlementSheet getSettlementSheetByNo(int enterpriseId, String settlementNo) {
    List<SettlementSheet> sheets = em.createQuery("select o from SettlementSheet o " +
            "where o.enterpriseId = :enterpriseId and o.settlementNo = :settlementNo", SettlementSheet.class)
            .setParameter("enterpriseId", enterpriseId)
            .setParameter("settlementNo", settlementNo)
            .getResultList();

    return sheets.size() == 1 ? sheets.get(0) : null;
  }

  @Override
  public List<OperationLog> searchSettlementHistory(int enterpriseId, int settlementId) {
    return em.createQuery("select o from OperationLog o " +
            "where o.enterpriseId = :enterpriseId and o.orderId = :orderId and o.objectType = :ot " +
            "order by o.id desc", OperationLog.class)
            .setParameter("enterpriseId", enterpriseId)
            .setParameter("orderId", settlementId)
            .setParameter("ot", OPERATING_OBJECT_SETTLEMENT)
            .getResultList();
  }


  /**
   * 调整结算单的金额
   */
  @Override
  public int adjustSettlementAmount(final int enterpriseId, final String username, final int id, final double amount, final String remark) {
    int retCode = 0;


    SettlementSheet ss = em.find(SettlementSheet.class, id);
    if (ss == null || ss.getEnterpriseId() != enterpriseId) {
      throw new RuntimeException("SettlementSheet is not existed.");
    }

    em.refresh(ss);

    if (ss.getAmount() < amount) {
      throw new RuntimeException("Amount is less than adjusting amount.");
    }

    double balance = ss.getAmount() - amount;

    retCode = em.createQuery("update SettlementSheet o set o.amount = :amount where o.id = :id")
            .setParameter("id", ss.getId())
            .setParameter("amount", balance)
            .executeUpdate();
    if (retCode != 1) {
      throw new RuntimeException("To adjust settlementAmount failed.");
    }


    OperationLog log0 = OperationLogFactory.createSettlementOperationLog();
    log0.setEnterpriseId(enterpriseId);
    log0.setOperator(username);
    log0.setOrderId(id);
    log0.setContent("调整金额（扣除：" + amount + ", 余额:" + balance + ", 原因：" + remark + ")");
    em.merge(log0);

    return 1;
  }


  /**
   * 修改结算单名称
   */
  @Override
  public int updateSettlementName(int enterpriseId, String username, int id, String settlementName) {
    int retCode = 0;


    retCode = em.createQuery("update SettlementSheet o set o.name = :name " +
            "where o.id = :id and o.enterpriseId = :enterpriseId")
            .setParameter("id", id)
            .setParameter("enterpriseId", enterpriseId)
            .setParameter("name", settlementName)
            .executeUpdate();
    if (retCode != 1) {
      throw new RuntimeException("To update settlement name failed.");
    }

    OperationLog log0 = OperationLogFactory.createSettlementOperationLog();
    log0.setEnterpriseId(enterpriseId);
    log0.setOperator(username);
    log0.setOrderId(id);
    log0.setContent("修改名称为：" + settlementName);
    em.merge(log0);


    return retCode;
  }


  /**
   * 从结算单中移除账单
   * 结算单金额 >= 0 时，移除账单后的新的余额也要保持 >= 0
   * 结算单金额 <= 0 时，移除账单后的新的余额也要保持 <= 0
   *
   */
  @Override
  public int removeBillOfSettlement(final int enterpriseId, final String username, final int settlementId, final String billNo) {
    int retCode = 0;

    SettlementSheet ss = em.find(SettlementSheet.class, settlementId);
    em.refresh(ss);

    if (ss == null || ss.getEnterpriseId() != enterpriseId) {
      throw new RuntimeException("SettlementSheet is not existed.");
    }

    List<BillInfo> billInfos = em.createQuery("select o from BillInfo o " +
            " where o.enterpriseId = :enterpriseId and o.billNum = :billNo and o.settlementNo = :settlementNo", BillInfo.class)
            .setParameter("enterpriseId", enterpriseId)
            .setParameter("billNo", billNo)
            .setParameter("settlementNo", ss.getSettlementNo())
            .getResultList();
    if (billInfos.size() != 1) {
      throw new RuntimeException("Not found billInfo in settlementNo");
    }

    BillInfo billInfo = billInfos.get(0);

    // 原来的结算单金额
    final double oldSettlementAmount = ss.getAmount();
    //控制下小数点后面的长度
    final double newAmount = ss.getAmount() - billInfo.getBillSum();

    if (oldSettlementAmount >= 0 && newAmount < 0) {
      throw new RuntimeException("新的金额不能为负数");
    } else if (oldSettlementAmount <= 0 && newAmount > 0) {
      throw new RuntimeException("新的金额不能为正数");
    }

    if (ss.getPaid() == SettlementSheet.PAID && billInfo.getBePaid().equalsIgnoreCase(BillInfo.CUSTOMER_PAYMENT_NOT_RECEIVED)) {
      OperationLog logWhat = OperationLogFactory.createSettlementOperationLog();
      logWhat.setEnterpriseId(enterpriseId);
      logWhat.setOperator(username);
      logWhat.setOrderId(settlementId);
      logWhat.setContent("What? Settlement has been paid, but bill(" + billNo + ") is not paid yet.");
      em.persist(logWhat);
    } else if (ss.getPaid() == SettlementSheet.PAID) {
      throw new RuntimeException("SettlementSheet is paid");
    }

    ss.setAmount(newAmount);
    ss.setLastUpdate(DateUtil.getCurDateTime());
    em.merge(ss);


    retCode = em.createQuery("update BillInfo o set o.settleDate = null, o.settlementNo = null " +
            " where o.enterpriseId = :enterpriseId and o.id = :id and o.settlementNo = :settlementNo")
            .setParameter("enterpriseId", enterpriseId)
            .setParameter("id", billInfo.getId())
            .setParameter("settlementNo", ss.getSettlementNo())
            .executeUpdate();
    if (retCode != 1) {
      throw new RuntimeException("update Bill failed");
    }


    OperationLog log0 = OperationLogFactory.createSettlementOperationLog();
    log0.setEnterpriseId(enterpriseId);
    log0.setOperator(username);
    log0.setOrderId(settlementId);
    log0.setContent("移除账单(" + billNo + " : " + billInfo.getBillSum() + "), 余额: " + newAmount);
    em.persist(log0);

    OperationLog billLog = OperationLogFactory.createBillOperationLog();
    billLog.setEnterpriseId(enterpriseId);
    billLog.setOperator(username);
    billLog.setOrderId(billInfo.getId());
    billLog.setContent("被从结算单（" + ss.getSettlementNo() + "）中移除。");
    em.persist(billLog);

    return 1;
  }


  /**
   * 对金额为0的结算单销账
   *
   * @param enterpriseId 企业id
   * @param username     当前用户名
   * @param settlementId 结算单id
   */
  @Override
  public int writeOffZeroSettlementAmount(int enterpriseId, String username, int settlementId) {
    int retCode = 0;

    SettlementSheet ss = em.find(SettlementSheet.class, settlementId);
    em.refresh(ss);

    if (ss == null || ss.getEnterpriseId() != enterpriseId) {
      throw new RuntimeException("SettlementSheet is not existed.");
    }

    if (ss.getPaid() == SettlementSheet.PAID) {
      throw new RuntimeException("SettlementSheet is paid");
    }
    if (ss.getAmount() != 0) {
      throw new RuntimeException("SettlementSheet's amount is not zero.");
    }

    ss.setPaid(1);
    ss.setLastUpdate(DateUtil.getCurDateTime());
    em.merge(ss);

    OperationLog log = OperationLogFactory.createSettlementOperationLog();
    log.setEnterpriseId(enterpriseId);
    log.setOperator(username);
    log.setOrderId(settlementId);
    log.setContent("手工销账");
    em.persist(log);

    retCode = em.createQuery("update BillInfo o set o.bePaid = '1', o.writeOffDate = :writeOffDate, " +
            "o.writeOffMemo = :writeOffMemo, o.lastUpdate = :lastUpdate " +
            "where o.enterpriseId = :enterpriseId and o.settlementNo = :settlementNo")
            .setParameter("enterpriseId", enterpriseId)
            .setParameter("settlementNo", ss.getSettlementNo())
            .setParameter("writeOffDate", DateUtil.getCurDate())
            .setParameter("writeOffMemo", "零结算单销账")
            .setParameter("lastUpdate", DateUtil.getCurDateTime())
            .executeUpdate();
    if (retCode < 1) {
      throw new RuntimeException("NO BILL INFO BE WRITTEN OFF.");
    }

    List<BillInfo> billInfos = em.createQuery("select o from BillInfo o " +
            "where o.enterpriseId = :enterpriseId and o.settlementNo = :settlementNo", BillInfo.class)
            .setParameter("enterpriseId", enterpriseId)
            .setParameter("settlementNo", ss.getSettlementNo())
            .getResultList();
    for (BillInfo o : billInfos) {
      OperationLog billLog = OperationLogFactory.createBillOperationLog();
      billLog.setEnterpriseId(enterpriseId);
      billLog.setOperator(username);
      billLog.setOrderId(o.getId());
      billLog.setContent("零结算单(" + ss.getSettlementNo() + ")销账.");
      em.persist(billLog);
    }

    return retCode;
  }


  /**
   * 结算单销账
   *
   * @param enterpriseId 当前用户的enterpriseId
   * @param username     当前用户名
   * @param settlementId 结算单的id
   * @param receiptId    收款单的id
   */
  @Override
  public int writeOffSettlement(int enterpriseId, String username, final int settlementId, final int receiptId) {
    //收款单的销账金额加上账单的金额，账单设置为已销账，账单对应的机票、火车票、酒店订单设置为已销账。
    // 记录到ReceiptHistory
    int retCode = 0;

    ReceiptInfo receiptInfo = em.find(ReceiptInfo.class, receiptId);
    em.refresh(receiptInfo);

    if (receiptInfo == null || receiptInfo.getEnterpriseId() != enterpriseId) {
      throw new RuntimeException("Receipt is not existed.");
    }

    SettlementSheet ss = em.find(SettlementSheet.class, settlementId);
    em.refresh(ss);

    if (ss == null || ss.getEnterpriseId() != enterpriseId) {
      throw new RuntimeException("SettlementSheet is not existed.");
    }

    if (ss.getPaid() == 1) {
      throw new RuntimeException("SettlementSheet is paid");
    }


    double totalAmount = receiptInfo.getWriteOffAmount() + ss.getAmount();
    if (receiptInfo.getAmount() < totalAmount) {
      throw new RuntimeException("Amount is less than write off amount.");
    }

    receiptInfo.setWriteOffAmount(totalAmount);
    receiptInfo.setLastUpdate(DateUtil.getCurDateTime());
    if (Double.compare(receiptInfo.getAmount(), receiptInfo.getWriteOffAmount()) == 0) {
      receiptInfo.setStatus(1);
    }

    em.merge(receiptInfo);

    OperationLog log0 = OperationLogFactory.createReceiptOperationLog();
    log0.setEnterpriseId(enterpriseId);
    log0.setOperator(username);
    log0.setOrderId(receiptId);
    log0.setContent("结算单销账：" + ss.getSettlementNo() + ", " + ss.getAmount());
    em.merge(log0);

    ReceiptHistory rh = new ReceiptHistory();
    rh.setEnterpriseId(enterpriseId);
    rh.setOperator(username);
    rh.setReceiptId(receiptInfo.getId());
    rh.setReceiptNo(receiptInfo.getReceiptNo());
    rh.setDebt(ss.getAmount());
    rh.setBalance(receiptInfo.getBalance());
    rh.setRemark("结算单(" + ss.getSettlementNo() + ")销账.");
    em.persist(rh);


    ss.setPaid(1);
    ss.setLastUpdate(DateUtil.getCurDateTime());
    em.merge(ss);

    OperationLog log = OperationLogFactory.createSettlementOperationLog();
    log.setEnterpriseId(enterpriseId);
    log.setOperator(username);
    log.setOrderId(settlementId);
    log.setContent("用收款单 " + receiptInfo.getReceiptNo() + " 销账");
    em.persist(log);

    retCode = em.createQuery("update BillInfo o set o.bePaid = '1', o.writeOffDate = :writeOffDate, " +
            "o.writeOffMemo = :writeOffMemo, o.lastUpdate = :lastUpdate, o.receiptNo = :receiptNo " +
            "where o.enterpriseId = :enterpriseId and o.settlementNo = :settlementNo")
            .setParameter("enterpriseId", enterpriseId)
            .setParameter("settlementNo", ss.getSettlementNo())
            .setParameter("writeOffDate", DateUtil.getCurDate())
            .setParameter("writeOffMemo", "结算单销账")
            .setParameter("lastUpdate", DateUtil.getCurDateTime())
            .setParameter("receiptNo", receiptInfo.getReceiptNo())
            .executeUpdate();
    if (retCode < 1) {
      throw new RuntimeException("NO BILL INFO BE WRITTEN OFF.");
    }

    List<BillInfo> billInfos = em.createQuery("select o from BillInfo o " +
            "where o.enterpriseId = :enterpriseId and o.settlementNo = :settlementNo", BillInfo.class)
            .setParameter("enterpriseId", enterpriseId)
            .setParameter("settlementNo", ss.getSettlementNo())
            .getResultList();
    for (BillInfo o : billInfos) {
      OperationLog billLog = OperationLogFactory.createBillOperationLog();
      billLog.setEnterpriseId(enterpriseId);
      billLog.setOperator(username);
      billLog.setOrderId(o.getId());
      billLog.setContent("用收款单(" + receiptInfo.getReceiptNo() + ")对结算单(" + ss.getSettlementNo() + ")销账.");
      em.persist(billLog);
    }


    return retCode;
  }


  /**
   * 撤销销账（结算单）
   */
  @Override
  public int undoWriteOffSettlement(final int enterpriseId, final String username, final int settlementId) {
    //收款单的销账金额加上账单的金额，账单设置为已销账，账单对应的机票、火车票、酒店订单设置为已销账。
    // 记录到ReceiptHistory
    int retCode = 0;

    SettlementSheet ss = em.find(SettlementSheet.class, settlementId);
    em.refresh(ss);

    if (ss == null || ss.getEnterpriseId() != enterpriseId) {
      throw new RuntimeException("SettlementSheet is not existed.");
    }

    if (ss.getPaid() == 0) {
      throw new RuntimeException("SettlementSheet is NOT paid");
    }


    //查找销账时用到的 收款单号
    List<String> receiptNos = em.createQuery("select distinct o.receiptNo from BillInfo o " +
            "where o.bePaid = '1' and o.enterpriseId = :enterpriseId and o.settlementNo = :settlementNo", String.class)
            .setParameter("enterpriseId", enterpriseId)
            .setParameter("settlementNo", ss.getSettlementNo())
            .getResultList();
    if (receiptNos.size() > 1) {
      throw new RuntimeException("too many receiptNo.");
    }

    final String receiptNo = receiptNos.get(0);

    List<ReceiptInfo> receiptInfos = em.createQuery("select o from ReceiptInfo o where o.enterpriseId = :enterpriseId and o.receiptNo = :receiptNo", ReceiptInfo.class)
            .setParameter("enterpriseId", enterpriseId)
            .setParameter("receiptNo", receiptNo)
            .getResultList();
    if (receiptInfos.size() > 1) {
      throw new RuntimeException("too many receipts.");
    }
    final ReceiptInfo receiptInfo = receiptInfos.get(0);


    double totalAmount = receiptInfo.getWriteOffAmount() - ss.getAmount();

    receiptInfo.setWriteOffAmount(totalAmount);
    receiptInfo.setLastUpdate(DateUtil.getCurDateTime());
    receiptInfo.setStatus(0);

    em.merge(receiptInfo);

    OperationLog log0 = OperationLogFactory.createReceiptOperationLog();
    log0.setEnterpriseId(enterpriseId);
    log0.setOperator(username);
    log0.setOrderId(receiptInfo.getId());
    log0.setContent("撤销结算单销账：" + ss.getSettlementNo() + ", " + ss.getAmount());
    em.merge(log0);

    ReceiptHistory rh = new ReceiptHistory();
    rh.setEnterpriseId(enterpriseId);
    rh.setOperator(username);
    rh.setReceiptId(receiptInfo.getId());
    rh.setReceiptNo(receiptInfo.getReceiptNo());
    rh.setCredit(ss.getAmount());
    rh.setBalance(receiptInfo.getBalance());
    rh.setRemark("撤销结算单(" + ss.getSettlementNo() + ")销账.");
    em.persist(rh);


    ss.setPaid(0);
    ss.setLastUpdate(DateUtil.getCurDateTime());
    em.merge(ss);

    OperationLog log = OperationLogFactory.createSettlementOperationLog();
    log.setEnterpriseId(enterpriseId);
    log.setOperator(username);
    log.setOrderId(settlementId);
    log.setContent("撤销（用收款单 " + receiptInfo.getReceiptNo() + " 销账）");
    em.persist(log);

    retCode = em.createQuery("update BillInfo o set o.bePaid = '0', o.writeOffDate = null, " +
            "o.writeOffMemo = null, o.lastUpdate = :lastUpdate, o.receiptNo = null " +
            "where o.enterpriseId = :enterpriseId and o.settlementNo = :settlementNo")
            .setParameter("enterpriseId", enterpriseId)
            .setParameter("settlementNo", ss.getSettlementNo())
            .setParameter("lastUpdate", DateUtil.getCurDateTime())
            .executeUpdate();
    if (retCode < 1) {
      throw new RuntimeException("NO BILL INFO UNDO WRITTEN OFF.");
    }

    List<BillInfo> billInfos = em.createQuery("select o from BillInfo o " +
            "where o.enterpriseId = :enterpriseId and o.settlementNo = :settlementNo", BillInfo.class)
            .setParameter("enterpriseId", enterpriseId)
            .setParameter("settlementNo", ss.getSettlementNo())
            .getResultList();
    for (BillInfo o : billInfos) {
      OperationLog billLog = OperationLogFactory.createBillOperationLog();
      billLog.setEnterpriseId(enterpriseId);
      billLog.setOperator(username);
      billLog.setOrderId(o.getId());
      billLog.setContent("撤销收款单(" + receiptInfo.getReceiptNo() + ")对结算单(" + ss.getSettlementNo() + ")销账.");
      em.persist(billLog);
    }


    return retCode;
  }

  @Override
  public int removeAllBills(final int enterpriseId, final String username, final int settlementId) {
    int retCode = 0;

    SettlementSheet ss = em.find(SettlementSheet.class, settlementId);
    em.refresh(ss);

    if (ss == null || ss.getEnterpriseId() != enterpriseId) {
      throw new RuntimeException("SettlementSheet is not existed.");
    }

    if (ss.getPaid() == SettlementSheet.PAID) {
      throw new RuntimeException("SettlementSheet is paid");
    }

    List<BillInfo> billInfos = em.createQuery("select o from BillInfo o " +
            " where o.enterpriseId = :enterpriseId and  o.settlementNo = :settlementNo", BillInfo.class)
            .setParameter("enterpriseId", enterpriseId)
            .setParameter("settlementNo", ss.getSettlementNo())
            .getResultList();
    if (billInfos.size() < 1) {
      throw new RuntimeException("Not found billInfo in settlementNo");
    }

    for (BillInfo billInfo : billInfos) {
      // 原来的结算单金额
      final double oldSettlementAmount = ss.getAmount();
      //控制下小数点后面的长度
      final double newAmount = ss.getAmount() - billInfo.getBillSum();

      ss.setAmount(newAmount);

      ss.setLastUpdate(DateUtil.getCurDateTime());
      em.merge(ss);
      em.flush();


      retCode = em.createQuery("update BillInfo o set o.settleDate = null, o.settlementNo = null " +
              " where o.enterpriseId = :enterpriseId and o.id = :id and o.settlementNo = :settlementNo")
              .setParameter("enterpriseId", enterpriseId)
              .setParameter("id", billInfo.getId())
              .setParameter("settlementNo", ss.getSettlementNo())
              .executeUpdate();
      if (retCode != 1) {
        throw new RuntimeException("update Bill failed");
      }

      OperationLog billLog = OperationLogFactory.createBillOperationLog();
      billLog.setEnterpriseId(enterpriseId);
      billLog.setOperator(username);
      billLog.setOrderId(billInfo.getId());
      billLog.setContent("从结算单（" + ss.getSettlementNo() + "）中全部移除。");
      em.persist(billLog);
    }

    OperationLog log0 = OperationLogFactory.createSettlementOperationLog();
    log0.setEnterpriseId(enterpriseId);
    log0.setOperator(username);
    log0.setOrderId(settlementId);
    log0.setContent("移除全部账单, 余额: " + ss.getAmount());
    em.persist(log0);


    return 1;
  }


  //创建新的结算单
  private int createNewSettlement(final int enterpriseId, final String username, final int customerId,
                                  final String settlementNo, final String settlementName,
                                  final double total, final Integer[] billIds0,
                                  final String remark) {
    int retCode = 0;


    List<Integer> billIds = Arrays.asList(billIds0);
    List<BillInfo> infos = searchBillsForSettlement(enterpriseId, customerId, billIds, em);

    boolean bContinue = true;
    double total1 = 0;

    for (BillInfo info : infos) {
      total1 += info.getBillSum();
      if (BILL_NOT_AUDITED.equalsIgnoreCase(info.getAudited())
              || !StringUtils.isEmpty(info.getSettlementNo())
              || CUSTOMER_PAYMENT_RECEIVED.equalsIgnoreCase(info.getBePaid())) {
        // 未审核的、结算单号不为空(已加入别的结算单)的，已收款的（非月结的）都不符合要求
        logger.error(info.getAudited() + ", " + info.getSettlementNo() + ", " + info.getBePaid());
        bContinue = false;
        break;
      }
    }

    if (bContinue) {
      //校验总数是否相等
      double alpha = Math.abs(total1 - total);
      if (alpha > 0.0001) {
        bContinue = false;
        logger.error("total1: " + total1 + ", total: " + total + ", alpha: " + alpha);
      }
    }

    if (!bContinue) {
      throw new RuntimeException("can not go on");
    }

    SettlementSheet ss = new SettlementSheet();
    ss.setEnterpriseId(enterpriseId);
    ss.setSettlementNo(settlementNo);
    if (customerId > 0) {
      Customer customer = em.find(Customer.class, customerId);
      ss.setCustomer(customer);
      ss.setCustomerType(Customer.ENT_CORPORATION);
    } else {
      ss.setCustomerType(Customer.ENT_INDIVIDUAL);
    }
    ss.setName(settlementName);
    ss.setAmount(total);

    em.persist(ss);

    // 结算单日志
    OperationLog log = OperationLogFactory.createSettlementOperationLog();
    log.setEnterpriseId(enterpriseId);
    log.setOperator(username);
    log.setOrderId(ss.getId());
    if (remark == null) {
      log.setContent("新建结算单，总金额：" + ss.getAmount());
    } else {
      log.setContent("新建结算单，总金额：" + ss.getAmount() + ". " + remark);
    }

    em.persist(log);

    for (BillInfo info : infos) {
      retCode = em.createQuery("update BillInfo o " +
              "set o.settlementNo = :settlementNo, o.settleDate = :settleDate " +
              "where o.enterpriseId = :enterpriseId " +
              "and o.id = :id " +
              "and o.settlementNo is null and o.settleDate is null")
              .setParameter("id", info.getId())
              .setParameter("enterpriseId", enterpriseId)
              .setParameter("settlementNo", settlementNo)
              .setParameter("settleDate", DateUtil.getCurDate())
              .executeUpdate();
      if (retCode != 1) {
        throw new RuntimeException("It failed to update bill's settlementNo.");
      }

      // 账单日志
      OperationLog billLog = OperationLogFactory.createBillOperationLog();
      billLog.setEnterpriseId(enterpriseId);
      billLog.setOperator(username);
      billLog.setOrderId(info.getId());
      if (remark == null) {
        billLog.setContent("被包含于结算单(" + settlementNo + ")");
      } else {
        billLog.setContent("被包含于结算单(" + settlementNo + "). " + remark);
      }

      em.persist(billLog);
    }

    retCode = 1;


    return retCode;
  }

  //加入已有的结算单
  private int appendSettlement(final int enterpriseId, final String username, final int customerId,
                               final int settlementId, final double total, final Integer[] billIds0) {
    int retCode = 0;


    SettlementSheet ss = em.find(SettlementSheet.class, settlementId);
    if (ss == null || ss.getEnterpriseId() != enterpriseId) {
      throw new RuntimeException("Settlement is not existed.");
    }

    em.refresh(ss);

    //检查结算单是否已销账
    if (ss.getPaid() == SettlementSheet.PAID) {
      throw new RuntimeException("Settlement(" + ss.getSettlementNo() + ") is paid.");
    }

    // 校验客户是否一样
    if (customerId == 0 && ss.getCustomerType() != 0) {
      throw new RuntimeException("Settlement customer type is not equal.");
    } else if (customerId > 0 && ss.getCustomer().getId() != customerId) {
      throw new RuntimeException("Settlement customer type is not equal.");
    }

    List<Integer> billIds = Arrays.asList(billIds0);
    List<BillInfo> infos = searchBillsForSettlement(enterpriseId, customerId, billIds, em);

    boolean blnContinue = true;
    double total1 = 0;
    for (BillInfo info : infos) {
      total1 += info.getBillSum();
      if (!"1".equalsIgnoreCase(info.getAudited())
              || info.getSettlementNo().length() > 0
              || CUSTOMER_PAYMENT_RECEIVED.equalsIgnoreCase(info.getBePaid())) {
        // 未审核的，已加入别的结算单的，已支付的（非月结的）都不符合要求
        blnContinue = false;
        break;
      }
    }

    if (blnContinue) {
      //校验总数是否相等
      double alpha = Math.abs(total1 - total);
      if (alpha > 0.0001) {
        throw new RuntimeException("检验未通过：|" + total1 + "-" + total + "| = " + alpha + " > 0.0001");
      }
    }

    if (blnContinue) {
      //负数结算单新增后还是要为负数
      final double oldAmount = ss.getAmount();
      ss.setAmount(total + ss.getAmount());

      if (oldAmount > 0 && ss.getAmount() < 0) {
        throw new RuntimeException("Settlement's amount must be >= 0.");
      }
      if (oldAmount < 0 && ss.getAmount() > 0) {
        throw new RuntimeException("负数结算单要保持为负数.");
      }
    }


    if (blnContinue) {
      // 加入已有的结算单
      final String settlementNo = ss.getSettlementNo();

      ss.setLastUpdate(DateUtil.getCurDateTime());
      em.merge(ss);

      String billNums = null;

      for (BillInfo info : infos) {
        billNums = billNums == null ? info.getBillNum() : billNums + ", " + info.getBillNum();

        retCode = em.createQuery("update BillInfo o " +
                "set o.settlementNo = :settlementNo, o.settleDate = :settleDate " +
                "where o.enterpriseId = :enterpriseId " +
                "and o.id = :id " +
                "and o.settlementNo is null and o.settleDate is null")
                .setParameter("id", info.getId())
                .setParameter("enterpriseId", enterpriseId)
                .setParameter("settlementNo", settlementNo)
                .setParameter("settleDate", DateUtil.getCurDate())
                .executeUpdate();
        if (retCode != 1) {
          throw new RuntimeException("It failed to update bill's settlementNo.");
        }

        OperationLog billLog = OperationLogFactory.createBillOperationLog();
        billLog.setEnterpriseId(enterpriseId);
        billLog.setOperator(username);
        billLog.setOrderId(info.getId());
        billLog.setContent("被包含到结算单(" + settlementNo + ")");
        em.persist(billLog);
      }

      OperationLog log = OperationLogFactory.createSettlementOperationLog();
      log.setEnterpriseId(enterpriseId);
      log.setOperator(username);
      log.setOrderId(ss.getId());
      log.setContent("添加" + infos.size() + "张账单(" + billNums + ")，增加金额：" + total + ", 总金额：" + ss.getAmount());
      em.persist(log);


      retCode = 1;
    } else {
      throw new RuntimeException("rollback");
    }

    return retCode;
  }


  private List<BillInfo> searchBillsForSettlement(final int enterpriseId, final int customerId, final List<Integer> billIds, EntityManager em) {
    if (customerId > 0) {
      //某月结客户的
      return em.createQuery("select o from BillInfo o where o.enterpriseId = :enterpriseId " +
              " and o.customerId = :customerId and o.id in :ids", BillInfo.class)
              .setParameter("enterpriseId", enterpriseId)
              .setParameter("customerId", customerId)
              .setParameter("ids", billIds)
              .getResultList();
    } else if (customerId == 0) {
      // 散客的
      return em.createQuery("select o from BillInfo o where o.enterpriseId = :enterpriseId " +
              " and o.customerType = 0 and o.id in :ids", BillInfo.class)
              .setParameter("enterpriseId", enterpriseId)
              .setParameter("ids", billIds)
              .getResultList();
    } else {
      return new ArrayList<>();
    }
  }

}
